package br.senai.sc.controller.crud;

import java.io.Serializable;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.jboss.seam.international.status.Messages;
import org.jboss.seam.international.status.builder.BundleKey;

import br.senai.sc.controller.business.UsuarioBusiness;
import br.senai.sc.controller.exception.BusinessException;
import br.senai.sc.model.Permissao;
import br.senai.sc.model.Usuario;
import br.senai.sc.util.SystemConstants;

/**
 * Responsável pelas funções que envolvem banco de dados da entidade
 * {@link Usuario}.
 * 
 * @author Tiago
 */
public class UsuarioCRUD implements Serializable {

	private static final long serialVersionUID = 1L;

	@PersistenceContext
	private EntityManager em;

	@Inject
	private UsuarioBusiness usuarioBusiness;

	@Inject
	private Validator validator;

	@Inject
	private Messages messages;

	/**
	 * Busca pelo {@link Usuario} com o ID informado.
	 * 
	 * @param idUsuario
	 *            ID do {@link Usuario} à ser buscado.
	 * @return {@link Usuario} encontrado ou null.
	 */
	public Usuario findUsuario(Integer idUsuario) {

		if (idUsuario == null) {
			throw new IllegalArgumentException(
					"Passado identificador null para procurar usuário.");
		}

		return em.find(Usuario.class, idUsuario);
	}

	/**
	 * Busca um {@link Usuario} por seu login. Caso null seja informado null
	 * será retornado.
	 * 
	 * @param login
	 *            Login usado na busca.
	 * @return Usuário encontrado ou nulo.
	 */
	public Usuario findByLogin(String login) {

		if (login == null) {
			return null;
		}

		try {
			return em.createNamedQuery(Usuario.USUARIO_BY_LOGIN, Usuario.class)
					.setParameter("login", login).getSingleResult();
		} catch (NoResultException e) {
			return null;
		}
	}

	/**
	 * Busca pela permissão com o ID informado.
	 * 
	 * @param idPermissao
	 *            ID da permissão à ser buscada.
	 * @return Permissão encontrada.
	 */
	public Permissao findPermissao(Integer idPermissao) {

		if (idPermissao == null) {
			throw new IllegalArgumentException(
					"Passado null para pesquisar permissão.");
		}

		return em.find(Permissao.class, idPermissao);
	}

	/**
	 * Busca todos os usuários cadastrados.
	 * 
	 * @return Lista de usuários encontrada.
	 */
	public List<Usuario> findUsuarios() {
		return em.createNamedQuery(Usuario.USUARIOS, Usuario.class)
				.getResultList();
	}

	/**
	 * Busca todas as permissões cadastradas.
	 * 
	 * @return Lista de permissões encontradas.
	 */
	public List<Permissao> findPermissoes() {
		return em.createNamedQuery(Permissao.PERMISSOES, Permissao.class)
				.getResultList();
	}

	/**
	 * Converte a lista de {@link ConstraintViolation} para uma lista de
	 * {@link BundleKey}.
	 * 
	 * @param constraints
	 * @return
	 */
	private void toBundleKeyList(Set<ConstraintViolation<Usuario>> constraints) {

		for (ConstraintViolation<Usuario> constraint : constraints) {
			messages.error(new BundleKey(
					SystemConstants.PATH_TO_COMMON_MESSAGES, constraint
							.getMessageTemplate()));
		}

	}

	/**
	 * Persiste os dados de um {@link Usuario}. Caso esteja salvando um novo
	 * {@link Usuario} valida as seguintes regras: - se a {@link Permissao}
	 * existe; - se não existe outro {@link Usuario} com o login informado.
	 * 
	 * Caso esteja editando um {@link Usuario}, valida as seguintes regras: - se
	 * o {@link Usuario} existe; - se não existe outro usuário com o mesmo
	 * login; - se a {@link Permissao} existe.
	 * 
	 * @param usuario
	 * @return Entidade {@link Usuario} attached.
	 * @throws BusinessException
	 *             Lançada caso houver algum problema com a validação do
	 *             {@link Usuario}.
	 */
	public Usuario salvar(Usuario usuario) throws BusinessException {

		if (usuario == null) {
			throw new IllegalArgumentException(
					"Informando um usuário null para salvar.");
		}

		Set<ConstraintViolation<Usuario>> constraints = validator
				.validate(usuario);
		if (constraints.size() > 0) {
			toBundleKeyList(constraints);
			throw new BusinessException();
		}

		if (usuario.getIdUsuario() == null) {
			inserir(usuario);
			return usuario;
		}

		return atualizar(usuario);
	}

	/**
	 * Salva um novo {@link Usuario} caso o mesmo seja validado.
	 * 
	 * @param usuario
	 * @throws BusinessException
	 *             Lançada caso o {@link Usuario} não seja válido.
	 */
	private void inserir(Usuario usuario) throws BusinessException {

		if (!usuarioBusiness.podeSalvar(usuario)) {
			throw new BusinessException();
		}
		em.persist(usuario);
	}

	/**
	 * Efetua as regras de negócio para atualizar um {@link Usuario}
	 * 
	 * @param usuario
	 * @return {@link Usuario} atualizado e attached;
	 * @throws BusinessException
	 *             Lançada caso alguma validação não passar.
	 */
	private Usuario atualizar(Usuario usuario) throws BusinessException {

		if (!usuarioBusiness.podeEditarUsuario(usuario)) {
			throw new BusinessException();
		}
		return em.merge(usuario);
	}

	/**
	 * Remove um {@link Usuario} do banco de dados. Este método efetua um merge
	 * na entidade antes de remover para garantir que esteja attached.
	 * 
	 * @param usuario
	 *            {@link Usuario} que será removido.
	 * @throws BusinessException
	 *             Lançada caso houver alguma validação que não seja atendida.
	 */
	public void remover(Usuario usuario) throws BusinessException {

		if (!usuarioBusiness.podeRemover(usuario)) {
			throw new BusinessException();
		}

		em.remove(em.merge(usuario)); // garante que a entidade seja attached
	}

}
